package com.wolfeyes.framework.shiro;

import com.wolfeyes.common.constant.HttpStatusConstants;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;

//import javax.naming.AuthenticationException;
import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
* @see Talk is cheap, Show me the code. -- Linus Torvalds
* @title 使用jwt过滤器作为shiro的过滤器
* @author yanyljava
* @create 2022-01-14 21:25
*/
@Slf4j
@Component
public class JwtFilter extends BasicHttpAuthenticationFilter implements Filter {

	@Override
	protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		// 从当前Ruequest获取token，从而创建Shiro-AuthenticationToken对象
		String token = httpServletRequest.getHeader("Token");
		JwtToken jwtToken = new JwtToken(token);

		//交给Shiro-realm进行登录操作
		getSubject(request, response).login(jwtToken);
		return true;
	}

	/**
	 * 登录认证
	 * @param request
	 * @param response
	 * @param mappedValue
	 * @return
	 */
	@Override
	protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
		try {
			return true;
			//return executeLogin(request, response);
		} catch (Exception e){
			log.error("JwtFilter过滤验证失败");
			return false;
		}
	}

	/**
	 * @title 处理前端跨域请求
	 * @description 在一些单体（项目体量较小）的项目实践中，可能使用shiro进行权限认证和校验，而shiro是基于cookie/session的， 在现在前后端分离开发的主流场景下，
	 * @description 前端开发人员需要在本地和后端进行调试，势必会遇到跨域的问题。而现在随着谷歌浏览器升级，已经禁止在跨域的情况下携带cookie。
	 * @description 所以，基于此背景，对项目实践的shiro框架进行一番改造，就可以实现基于Token的认证方式又不需要改动Shiro框架的其他功能了。
	 * @author yanyljava
	 * @param request
	 * @param response
	 * @return boolean
	 * @throws Exception
	 */
	@Override
	protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
		HttpServletRequest httpServletRequest = (HttpServletRequest) request;
		HttpServletResponse httpServletResponse = (HttpServletResponse) response;
		//httpServletResponse.setContentType("application/json;charset=utf-8");
		
		// 请注意：Access-Control-Allow-Credentials为true时Access-Control-Allow-Origin不能为*
		// Shiro权限框架默认是使用的cookie/session来实现的登录认证，这意味着我们前后端分离的项目需要设置Access-Control-Allow-Credentials为true，
		// 并且前端ajax需要设置携带cookie请求。也就是axios.defaults.withCredentials=true，才行。我们这里由于使用基于token的认证方式，所以
		// 我们的Access-Control-Allow-Credentials设置为false（默认为false），这也就意味着我们的每个请求都是不同的请求会话（无状态的）。
		//httpServletResponse.setHeader("Access-Control-Allow-Credentials", "true");
		
		// 出于浏览器的同源策略限制。同源策略（Sameoriginpolicy）是一种约定，它是浏览器最核心也最基本的安全功能，
		// 所谓同源（即指在同一个域）就是两个页面具有相同的协议（protocol），主机（host）和端口号（port）。
		// 什么是跨域？当一个请求url的协议、域名、端口三者之间任意一个与当前页面url不同即为跨域。
		// 在以前传统的项目程序为什么没有过跨域问题的困扰呢。那是因为以前传统的项目程序几乎都不是前后端分离的。比如之前的jsp, freemarker实现的前端，
		// 他们和后端的代码都是放到一起的，所以他们一起部署，具有相同的域名，协议和端口号，自然不存在跨域的问题。
		// 但是随着企业云原生的火热及微服务大行其道，现在的程序几乎都是前后端分离的项目程序（99%以上，你懂我意思吧~），前后端分离的程序有什么特点，
		// 就是前后端都是单独部署的，前端服务和后端服务都会监听属于自己的端口号，所以很容易产生跨域的问题。
		// 当前端端不同源的时候，而前端的服务又需要去访问和他不同源的后端的接口，自然就产生了跨域的问题，
		// 所以在前后端分离的项目中，跨域问题是我们永远都绕不开的话题。
		// （PS：毕竟都2022了，就像你去面试BAT大厂，高并发，秒杀系统设计、消息中间件100%投递、缓存双写一致性问题、微服务治理等等话题一样~）。
		// 在使用shiro-web的情况下，许多解决跨域问题的办法会不生效（比如我之前使用的拦截器的方式或springBoot的方式），我们此处采用Filter的方式来解决跨域的问题
		// 使用shiro的情况下，拦截器来处理跨域有时候不生效，建议使用Filter方式处理跨域问题。
		// String origin = httpServletRequest.getHeader("Origin");=> "*"
		httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
		// 前后端分离的项目Restful风格下接口标准：增C删D改U查R-CRUD => POST,DELETE,PUT,GET 以及跨域请求的前置OPTIONS请求
		httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
		// 超时设置24*60*60，单位：秒
		//httpServletResponse.setHeader("Access-Control-Max-Age", "86400");
		// 此处为允许请求携带的所有请求头，如果要限制，可自行定义
		httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
		// 此处设置是为了解决OPTIONS请求无法获取x-token请求头信息的问题报错：
		//Access to XMLHttpRequest at 'http://localhost:8099/shiro1/testSession/getSession' from origin 'http://localhost:63342' 
		//has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
		//其根本原因就是前端跨域请求时，如果是复杂的跨域请求，前端会先发送一个OPTIONS请求来确认是否能够访问，但是OPTIONS请求有个问题，
		//项目实践中我们自定义的请求头x-token无法获取值，这导致该请求进入shiro框架的身份认证时无法通过认证。
		//这就意味着，如果是OPTIONS请求，我们需要跳过shiro认证（类似白名单机制）。找到问题所在了，接下来就容易解决处理了。
		// 跨域时会首先发送一个option请求, 直接返回正常状态
		if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
			httpServletResponse.setStatus(HttpStatusConstants.SUCCESS);
			return false;
		}
		return super.preHandle(request, response);
	}
	
}


